package com.haotian.barcode;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import org.apache.log4j.Logger;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Result;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;


/**
 * 二维码工具包
 * 
 * @author 张昊天
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public final class BarCodeImgUtil {

	private final static Logger log = Logger.getLogger(BarCodeImgUtil.class);

	/*
	 * public static void main(String[] args) throws
	 * UnsupportedEncodingException, DecoderException {
	 * 
	 * String info = "$01" +
	 * "5Lit56e75LqS6IGU572R5pyJ6ZmQ5YWs5Y+4PC8+OTE0NDAxMDZNQTU5QUtFSDFZPC8+5bm/5bee\n"
	 * +
	 * "5biC5aSp5rKz5Yy66auY5ZSQ6LevMzMz5Y+36Ieq57yWMS4x5qCLMDIwLTY2NjAwMDA4PC8+5Lit\n"
	 * +
	 * "5Zu95Yac5Lia6ZO26KGM6IKh5Lu95pyJ6ZmQ5YWs5Y+45bm/5bee5Y2X5pa55Z+65Zyw5pSv6KGM\n"
	 * + "NDQwMzc4MDEwNDAwMDE4OTE8Lz5EMjY2\n" + "$";
	 * 
	 * // String info = "http://www.baidu.com";
	 * 
	 * BarCodeImgUtil.toFile(info, "c://", "code", 500, 500, new
	 * File("c://logo.jpg"));
	 * 
	 * String result = BarCodeImgUtil.decodeQR(new File("c://code.jpg"));
	 * 
	 * System.out.println(result);
	 * 
	 * }
	 */

	private final static Map hints = new HashMap();
	private final static Map hintsLogo = new HashMap();

	private final static String IMG_TYPE_JPG = "jpg";
	private final static String IMG_TYPE_fULL_JPG = ".jpg";
	private final static char JOIN = '.';

	static {

		hints.put(EncodeHintType.MARGIN, 0); // 设置白边
		hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
		hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);

		hintsLogo.put(EncodeHintType.MARGIN, 0); // 设置白边
		hintsLogo.put(EncodeHintType.CHARACTER_SET, "utf-8");
		hintsLogo.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);

	}

	/*** 对外使用 ***/

	/**
	 * 
	 * 输出二维码到文件
	 * 
	 * @param content
	 *            编码内容
	 * @param savaPath
	 *            文件保存路径
	 * @param fileName
	 *            文件名，不包含后缀
	 * @param w
	 *            宽
	 * @param h
	 *            高
	 */
	public final static void toFile(String content, String savaPath, String fileName, int w, int h) {

		try {
			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hints);
			File file1 = new File(savaPath, fileName + IMG_TYPE_fULL_JPG);
			if (!file1.exists()) {
				file1.mkdirs();
			}
			MatrixToImageWriter.writeToFile(bitMatrix, IMG_TYPE_JPG, file1,w, h);
		} catch (Exception e) {
			log.error("生成二维码到文件出错", e);
		}

	}

	/**
	 * 
	 * <p>
	 * 输出二维码到文件 (可设置参数值)
	 * </p>
	 * 
	 * @param content
	 *            内容
	 * @param savaPath
	 *            文件保存路径
	 * @param fileName
	 *            文件名称 （不包括后缀）
	 * @param w
	 *            宽
	 * @param h高
	 * @param imgType
	 *            文件类型（.jpg .png）
	 * @param margin
	 *            白边 （取整数 最小单位0 个位数递增不要设置太大）
	 * @param charset
	 *            内容编码
	 * @param level
	 *            纠错级别
	 */
	public final static void toFile(String content, String savaPath, String fileName, int w, int h, String imgType,
			int margin, String charset, ErrorCorrectionLevel level) {

		try {

			// 配置生成参数
			Map hints = new HashMap();
			hints.put(EncodeHintType.MARGIN, margin); // 设置白边
			hints.put(EncodeHintType.CHARACTER_SET, charset);
			hints.put(EncodeHintType.ERROR_CORRECTION, level);

			StringBuilder sb = new StringBuilder();
			sb.append(fileName).append(JOIN).append(imgType);

			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hints);
			File file1 = new File(savaPath, fileName + IMG_TYPE_fULL_JPG);
			if (!file1.exists()) {
				file1.mkdirs();
			}
			MatrixToImageWriter.writeToFile(bitMatrix, IMG_TYPE_JPG, file1,w, h);
		} catch (Exception e) {
			log.error("生成二维码到文件出错", e);
		}

	}

	/**
	 * 
	 * 输出带logo的二维码到文件
	 * 
	 * @param content
	 *            内容
	 * @param savaPath
	 *            文件保存路径
	 * @param fileName
	 *            生成的文件名称
	 * @param w
	 *            二维码宽
	 * @param h
	 *            二维码高
	 * @param logoFile
	 *            要插入的logo图片文件
	 */
	public final static void toFile(String content, String savaPath, String fileName, int w, int h, File logoFile) {

		try {
			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hintsLogo);
			File file1 = new File(savaPath, fileName + IMG_TYPE_fULL_JPG);
			if (!file1.exists()) {
				file1.mkdirs();
			}
			MatrixToImageWriter.writeToFile(bitMatrix, IMG_TYPE_JPG, file1, logoFile,w, h);
		} catch (Exception e) {
			log.error("生成二维码到文件出错", e);
		}
	}

	/**
	 * 输出带logo的二维码到文件 (可设置参数值)
	 * 
	 * @param content
	 *            生成内容
	 * @param savaPath
	 *            文件路径
	 * @param fileName
	 *            文件名(不包括后缀)
	 * @param w
	 *            宽
	 * @param h
	 *            高
	 * @param logoFile
	 *            logo图片文件
	 * @param imgType
	 *            生成的图片类型 （jpg,png）
	 * @param margin
	 *            白边大小 （取整数 0，1，2，3）
	 * @param charset
	 *            编码
	 * @param level
	 *            纠错级别 ErrorCorrectionLevel枚举中有四种级别
	 */
	public final static void toFile(String content, String savaPath, String fileName, int w, int h, File logoFile,
			String imgType, int margin, String charset, ErrorCorrectionLevel level) {

		try {

			// 配置生成参数
			Map hints = new HashMap();
			hints.put(EncodeHintType.MARGIN, margin); // 设置白边
			hints.put(EncodeHintType.CHARACTER_SET, charset);
			hints.put(EncodeHintType.ERROR_CORRECTION, level);

			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

			StringBuilder sb = new StringBuilder();
			sb.append(fileName).append(JOIN).append(imgType);

			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hints);
			File file = new File(savaPath, sb.toString());
			if (!file.exists()) {
				file.mkdirs();
			}
			MatrixToImageWriter.writeToFile(bitMatrix, imgType, file, logoFile,w, h);
		} catch (Exception e) {
			log.error("生成二维码到文件出错", e);
		}
	}

	/**
	 * <p>
	 * 输出二维码到IO流 （可自行设置 编码参数）
	 * </p>
	 * 
	 * @param content
	 *            编码内容
	 * @param out
	 *            输出流
	 * @param w
	 *            宽
	 * @param h
	 *            高
	 * @param imgType
	 *            图片类型 ('.jpg','.png')
	 * @param margin
	 *            白边 （整型数值）
	 * @param charset
	 *            编码格式 String
	 * @param level
	 *            纠错级别 ErrorCorrectionLevel
	 */
	public final static void toStream(String content, OutputStream out, int w, int h, String imgType, int margin,
			String charset, ErrorCorrectionLevel level) {
		try {

			// 配置生成参数
			Map hints = new HashMap();
			hints.put(EncodeHintType.MARGIN, margin); // 设置白边
			hints.put(EncodeHintType.CHARACTER_SET, charset);
			hints.put(EncodeHintType.ERROR_CORRECTION, level);

			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hints);

			MatrixToImageWriter.writeToStream(bitMatrix, imgType, out,w, h);
		} catch (Exception e) {
			log.error("生成二维码到IO流出错", e);
		}
	}

	/**
	 * 
	 * 输出带Logo的二维码到IO流
	 * 
	 * @param content
	 *            编码内容
	 * @param out
	 *            输出流
	 * @param w
	 *            宽
	 * @param h
	 *            高
	 */
	public final static void toStream(String content, OutputStream out, int w, int h, File logoFile) {
		try {
			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hintsLogo);

			MatrixToImageWriter.writeToStream(bitMatrix, IMG_TYPE_JPG, out, logoFile,w, h);
		} catch (Exception e) {
			log.error("生成二维码到IO流出错", e);
		}
	}

	/**
	 * <p>
	 * 输出带Logo的二维码到IO流 、可自定义配置生成参数
	 * </p>
	 * 
	 * @param content
	 *            编码内容
	 * @param imgType
	 *            图片类型 后缀 ('.jpg', '.png')
	 * @param out
	 *            输出流
	 * @param w
	 *            宽
	 * @param h
	 *            高
	 * @param margin
	 *            白边 （整型数值）
	 * @param charset
	 *            编码格式 String
	 * @param level
	 *            纠错级别
	 * @param logoFile
	 *            LOGO图片文件
	 */
	public final static void toStream(String content, OutputStream out, int w, int h, File logoFile, String imgType,
			int margin, String charset, ErrorCorrectionLevel level) {
		try {

			// 配置生成参数
			Map hintsLogo = new HashMap();
			hintsLogo.put(EncodeHintType.MARGIN, margin); // 设置白边
			hintsLogo.put(EncodeHintType.CHARACTER_SET, charset);
			hintsLogo.put(EncodeHintType.ERROR_CORRECTION, level);

			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hintsLogo);

			MatrixToImageWriter.writeToStream(bitMatrix, imgType, out, logoFile,w, h);

		} catch (Exception e) {
			log.error("生成二维码到IO流出错", e);
		}
	}

	/**
	 * 解码二维码
	 *
	 * @param file
	 *            需要解码的二维码文件
	 * @return 解码后字符串
	 */
	public final static String decodeQR(File file) {
		String retStr = "";
		try {
			BufferedImage bufferedImage = ImageIO.read(new FileInputStream(file));
			LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
			Binarizer binarizer = new HybridBinarizer(source);
			BinaryBitmap bitmap = new BinaryBitmap(binarizer);
			HashMap<DecodeHintType, Object> hintTypeObjectHashMap = new HashMap<>();
			hintTypeObjectHashMap.put(DecodeHintType.CHARACTER_SET, "utf-8");
			Result result = new MultiFormatReader().decode(bitmap, hintTypeObjectHashMap);
			retStr = result.getText();
		} catch (Exception e) {
			log.error("解析二维码出错", e);
		}
		return retStr;
	}

	/** 静态内部类 生成过程的核心操作 **/
	private static final class MatrixToImageWriter {

		private static final int BLACK = 0xFF000000;
		private static final int WHITE = 0xFFFFFFFF;

		private static BufferedImage toBufferedImage(BitMatrix matrix ,int w,int h) {
			int width = matrix.getWidth();
			int height = matrix.getHeight();
			BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			for (int x = 0; x < width; x++) {
				for (int y = 0; y < height; y++) {
					image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
				}
			}
			
			
			  BufferedImage tag = new BufferedImage(w, h,   
	                    BufferedImage.TYPE_INT_RGB);   
		      tag.getGraphics().drawImage(image, 0, 0, w, h, null); 
			
			return tag;
		}

		/*
		 * 到文件 不带logo
		 */
		private static void writeToFile(BitMatrix matrix, String format, File file,int w, int h) throws IOException {
			BufferedImage image = toBufferedImage(matrix,w,h);
			
			if (!ImageIO.write(image, format, file)) {
				throw new IOException("Could not write an image of format " + format + " to " + file);
			}
		}

		/*
		 * 到文件 带logo
		 */
		private static void writeToFile(BitMatrix matrix, String format, File file, File logoFile,int w, int h) throws IOException {
			BufferedImage image = toBufferedImage(matrix,w,h);

			// 加入logo
			readLogo(image, logoFile);

			if (!ImageIO.write(image, format, file)) {
				throw new IOException("Could not write an image of format " + format + " to " + file);
			}
		}

		/*
		 * 到IO流 不带logo
		 */
		private static void writeToStream(BitMatrix matrix, String format, OutputStream stream,int w, int h) throws IOException {
			BufferedImage image = toBufferedImage(matrix,w,h);
			
			if (!ImageIO.write(image, format, stream)) {
				throw new IOException("Could not write an image of format " + format);
			}
		}

		/*
		 * 到IO流 带logo
		 */
		private static void writeToStream(BitMatrix matrix, String format, OutputStream stream, File logoFile,int w, int h)
				throws IOException {
			BufferedImage image = toBufferedImage(matrix,w,h);

			// 加入logo
			readLogo(image, logoFile);

			if (!ImageIO.write(image, format, stream)) {
				throw new IOException("Could not write an image of format " + format);
			}
		}

		/*
		 * 在image中 加入logo 图片
		 */
		private static void readLogo(BufferedImage image, File logoFile) throws IOException {
			// 载入logo
			BufferedImage tmp = ImageIO.read(logoFile);

			// 计算二维码总面积
			double zongmj = image.getWidth() * image.getHeight();

			// 计算logo长宽
			int logoWH = (int) Math.sqrt(zongmj * 0.10);

			Image log = tmp.getScaledInstance(logoWH, logoWH, BufferedImage.TYPE_INT_RGB);

			Graphics graphics = image.getGraphics();
			graphics.drawImage(log, (image.getWidth() - log.getWidth(null)) / 2,
					(image.getHeight() - log.getHeight(null)) / 2, null);
			graphics.dispose();
			log.flush();
		}
	}

	/**
	 * 
	 * 输出二维码到IO流
	 * 
	 * @param content
	 *            编码内容
	 * @param out
	 *            输出流
	 * @param w
	 *            宽
	 * @param h
	 *            高
	 */
	public final static void toStream(String content, OutputStream out, int w, int h) {
		try {
			MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

			BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, w, h, hints);

			MatrixToImageWriter.writeToStream(bitMatrix, "jpg", out,w, h);
		} catch (Exception e) {
			log.error("生成二维码到IO流出错", e);
		}
	}


	public static void main(String[] args) throws IOException {

		FileOutputStream out = new FileOutputStream(new File("c://code.jpg"));
		
		
		BarCodeImgUtil.toStream(
				"$016Iul6ZuF6L2v5Lu2PC8+MTIzNDU2Nzg5MDEyMzQ1PC8+5rGf6IuP5Y2X5Lqs6Zuo6Iqx5Y+wODc2NTQzMTI8Lz7ljZfkuqzpk7booYwwOTg3NjU0MzEyMzQ1Njc4MDk4NzY8Lz5DNUI4\r\n$",
				out, 1024, 1024);
		out.close();

	}

}
